/**
 * 连接管理器，充当Server的作用，用于监听远端客户端的连接请求
 * 需要显示的调用listen()才会启动
 * 可以通过设置监听端口，改变默认监听的端口号
 */

#ifndef TCPMANAGER_H
#define TCPMANAGER_H

#include "transfersocket.h"
#include "TransferType.h"

#include <QMap>
#include <QReadWriteLock>
#include <QHostAddress>
#include <QTcpServer>

class Acceptor;
class TcpManager;

class TCPMANAGER_EXPORT TcpManager : public QObject
{
    Q_OBJECT
public:
    using Functor = std::function<void(qint64 magicNumber, QByteArray data)>;

    TcpManager(QObject *parent = nullptr);
    ~TcpManager();

    /**
     * @brief   : 开启TcpServer的服务，接收客户端的连接请求。
     * @param   : address 监听的地址，默认为任意地址
     * @param   : port 监听的端口号，默认为43800端口
     * @return  : true 打开监听成功
     *            false 打开监听失败
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-20 21:28:05
     * @details : 线程安全性由QTcpServer来保证，可以使用moveToThread()将TcpManager移动到后台线程去执行
     */
    bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 43800);

    /**
     * @brief   : 关闭TcpServer的服务，不再接收客户端的连接请求。
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-20 21:32:37
     * @details : 线程安全性由QTcpServer来保证。
     *            会自动断开所有正在活跃的连接。
     */
    void close();

    /**
     * @brief   : 将TcpManager转到后台线程去执行
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-21 15:56:03
     * @details : 该函数只在第一次调用时有效
     */
    void async();

    static TcpManager* instance();

    /**
     * @brief   : 获取所有的套接字
     * @return  : 所有套接字
     * @author  : BinWatson
     * @version : v1.2
     * @date    : 2022-06-22 08:32:57
     * @details : 该函数不可重入
     */
    QList<QSharedPointer<TransferSocket>> getSockets();

    /**
     * @brief   : 根据套接字名获取套接字
     * @param   : name 套接字名
     * @return  : 一个包裹了原生套接字的智能指针，用法同正常的指针
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-22 08:34:13
     * @details : 该函数不可重入
     */
    QSharedPointer<TransferSocket> getSocket(const QString& name);

    /**
     * @brief   : 产生一个magic number，用于套接字传输使用，保证magic number不重复。
     * @param   : name description
     * @return  : 返回值说明
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-20 21:36:13
     * @details : 一条Tcp连接要复用传输多个文件和渲染引擎的数据，
     *            可能出现分块传输的问题，因此每一个文件都有一个
     *            属于自己的唯一magic number，用与区分不同的文件
     */
    static qint64 magicNumber();

    /**
     * @brief   : 注册TcpBlockType对应的回调函数
     * @param   : type 类型
     *            functor
     * @return  : 返回值说明
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-20 09:42:12
     * @details : 详细说明
     */
    Q_INVOKABLE void registerFunctor(TransferType type, TcpManager::Functor functor);

    /**
     * @brief   : 发起一个Tcp连接
     * @param   : socketName 套接字名字，用于在TcpManager中查找套接字
     * @param   : address    远端的IP地址
     * @param   : port       远端的端口号
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-20 21:39:38
     * @details : Thread-safe，Non-block，
     *            连接成功会产生connectToHostSuccess信号
     *            连接失败会产生connectToHostFailure信号
     *            socketName后续可以通过getSocket()来获取该TransferSocket
     */
    Q_INVOKABLE void connectToHost(const QString& socketName, const QString& address, const quint16 port);

    /**
     * @brief   : 用于断开一个Tcp套接字连接
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-20 21:44:51
     */
    Q_INVOKABLE void disconnectFromHost(const QString& socketName);
    Q_INVOKABLE void disconnectFromHost(const QString& address, const quint16 port);

    /* used by Tranfersocket */
    Q_INVOKABLE void handleNewTcpBlock(QSharedPointer<TcpBlock> block);

public slots:
    void onSocketDisconnect();

signals:
    /* send signal, when a new tcp connection is accepted. */
    void newConnection(QSharedPointer<TransferSocket> socket);
    /* send signal, when a tcp connection is disconnected. */
    void DisConnection(const QString &socketName, const QHostAddress& peerAddress, const quint16 peerPort);

    /* 请求连接成功时发送 */
    void connectToHostSuccess(QSharedPointer<TransferSocket> socket);
    /* 请求连接失败时发送 */
    void connectToHostFailure(QString address, quint16 port);

private slots:
    /**
     * @brief   : 捕获Acceptor的newSocket信号
     * @author  : BinWatson
     * @version : v1.0
     * @date    : 2022-06-21 16:11:56
     * @details : 设置新套接字的一些属性，和进行内存管理
     */
    void onNewSocket(QSharedPointer<TransferSocket> socket);

private:
    /* 仅在对象内使用 */
    bool runInLoop() const;
    void connectHandle() const;
    void delSocket(const QString& name);
    void addSocket(QSharedPointer<TransferSocket> socket);

private:
    using SocketMap = QMap<QString, QSharedPointer<TransferSocket>>;
    using FuncHash = QHash<TransferType, Functor>;

    static volatile qint64 MagicNumber;

    Acceptor *m_acceptor;    /* 负责监听 */
    QThread  *m_thread;

    QReadWriteLock m_rwlock;                    /* used by m_sockets */
    SocketMap m_sockets;   /* 管理所有的套接字 */
    FuncHash  m_functors;

    Q_DISABLE_COPY_MOVE(TcpManager);
};


class Acceptor : public QTcpServer
{
    Q_OBJECT
public:
    Acceptor(QObject *parent = nullptr);
    ~Acceptor();

protected:
    virtual void incomingConnection(qintptr socketDescriptor) override;

signals:
    void newSocket(QSharedPointer<TransferSocket> socket);
};

#endif // TCPMANAGER_H


